-
Couldn't load subscription status.
- Fork 57
Add service module to write PolKit agents #302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
While I am not opposed to the addition of a polkit integration, I would rather avoid all KDE dependencies as including them leads to massive dependency trees on most distros, and they're usually pretty replaceable. polkit-qt-1 appears to be a very thin wrapper around the polkit api that doesn't provide much. I would prefer to avoid it unless you have a good reason to pull it in. |
|
polkit-qt-1 is indeed a very thin wrapper. The 'good' reason for using it is that it deals with some of the more error-prone aspects of the upstream libraries and doesn't really bring anything that we don't need. While it is maintained by the KDE team, the package doesn't actually pull in anything but Polkit itself and Qt on the distros I checked (Debian, Ubuntu, Arch, Fedora, RHEL, NixOS, or Gentoo). I find it unlikely that others would have wildly different dependencies. That said, it should also be fairly easy to not use it at the cost of a few hundred LOC, which would have the benefit of me getting to correct one of the limitations of polkit-qt-1. |
|
It sounds like one of the more reasonable ones if we had to use it then, though I would personally still prefer not to. |
|
Alright. I'll see what I can do. |
|
I removed the dependency on polkit-qt, interacting with the PolKit libraries directly, instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review on the interface.
Before continuing further, I've noticed the polkit agent interface appears to be available directly over dbus. Is there a reason to use any library at all for it? (Especially a gobject library)
|
Thanks for the review! I don't like using a gobject library here either, but yes, there is a good reason for using it instead of interacting directly over DBus. PolKit requires the client that sends the confirmation message to be privileged. To this end, To avoid libpolkit, Quickshell would either need to run as root or we would need our own setuid helper. Besides the implementation overhead, this would break simply running it from your Flake via Nix or installing it via Home-Manager. I imagine (although I don't positively know) many distros might also look more sceptically at any setuid binaries. Finally, the DBus interface isn't actually stable because it is currently considered an implementation detail. I don't think it has changed significantly in recent years, but I would still prefer the more stable solution. |
|
That's horrible on so many levels. |
|
I... yeah. But that's Polkit for you. There's many things I wish I didn't know about it. If, given these circumstances, you'd rather not have the code upstream, I'm just going to package it up as a separate library. |
|
I'm fine with the gobject code since its an actual requirement, and this is a feature many people want. I'll finish the review tomorrow probably. |
|
Thank you! I'm not in any rush. Also, fair warning: this is the first 'not insignificant' thing I've done with Qt, so while I tried to do things right, some things might be the way they are because I didn't know any better. I'll happily take any pointers. |
|
Thats fine. One further thing that would be helpful would be to add a manual test qml file similar to what exists in other parts of the source. Just cover a reasonable amount of the api for a simple manual regression test |
|
I got started on some of your comments. Here's an additional thing we might consider: PolKit can send either 'show-info' or 'show-error'. I am currently storing both into the same property with an One thing I'm a bit unsure on is object destruction/cleanup and its behavior in Qt. When an authentication request finishes, we need to clean up all objects, but does this cause the UI to flicker, if we animate a window out, for example? |
Does the api provide any sort of hint as to if both should be present or not? If it does something like sending a new version of the error message thats an empty string to reset thats a pretty clear signal we could have multiple, but if not I'm less sure. If the messages are coming from pam, our pam module currently replaces the last message with the new one so it probably makes sense to keep that design.
This is a bit of a mess, Quickshell in particular has a hack for it called Retainable which you can see here https://quickshell.org/docs/v0.2.1/types/Quickshell/Retainable/. Retainable objects generally hold their last state and become immutable while retained to make it easier to write reactive UI against them. Note: I tested the dialog and see you have a lot of |
Unfortunately not. The messages are indeed forwarded from PAM. I am therefore inclined to leave it as is.
Thanks! I'll look into that to figure out how best to use it in this scenario.
That's exactly what I was looking for. I'll migrate to that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super in depth as I've been busy, but I gave it a look-over.
|
I performed a fairly significant refactoring to accommodate the changes you recommended.
I'll look at the lint errors soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-reviewed the pr as a whole
|
Thank you for the extensive review! I addressed most of your comments. I also introduced a RAII helper I selectively turned off some lints in specific instances where I felt they were not helpful or actively wrong (e.g. it won't let me forward declare any structs from the Polkit libraries). There should always be an accompanying comment explaining why the specific rule doesn't make sense in that case. Let me know if you disagree with any of my reasons. |
|
I do love a good reactivity system. I switched over to bindable properties where I figured it makes sense. With that I believe I have addressed everything you pointed out. I appreciate your patience, this has been quite the learning experience. |
|
I rebased onto current master, squashed and fixed a small bug in the reloading logic. I have also integrated this into Noctalia in a branch to validate in a more complex config. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly just (poorly disclosed) style nits and a couple questions left
src/services/polkit/qml.cpp
Outdated
| PolkitAgent::PolkitAgent(QObject* parent): QObject(parent) { | ||
| // Try to takeover an existing PolkitAgentImpl from the previous QML engine generation. | ||
| // If none exists, we wait until componentComplete to create one so we have the correct path. | ||
| PolkitAgentImpl::tryTakeover(this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just wait until component complete and takeoverOrCreate then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While integrating into Noctalia, I ran into a situation where the destructor of the old generation was called before componentComplete of the next one was, leading to resource destruction. It may very well be that this was due to the way Noctalia handle reloads (where I e.g. also observed the bar disappearing and reappearing), but in any case I moved this as early as possible to avoid rejected requests wherever possible.
Is it true that (if nothing else is going on) componentComplete of the next generation is always called before the destructor of the previous is called? If yes, I can indeed simplify this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
componentComplete absolutely should be trigged before destruction of the next generation. We can't rely on property values until then so onReload needs to wait for it before destroying the old generation. If that is not the case, Noctilla probably delays things with loaders in a way that breaks the reload system. @Ly-sec?
ref
quickshell/src/core/generation.cpp
Line 144 in 1b147a2
| reloadable->reload(old ? old->root : nullptr); |
quickshell/src/core/rootwrapper.cpp
Lines 136 to 143 in 1b147a2
| component.completeCreate(); | |
| if (this->generation) { | |
| QObject::disconnect(this->generation, nullptr, this, nullptr); | |
| } | |
| auto isReload = this->generation != nullptr; | |
| generation->onReload(hard ? nullptr : this->generation); |
src/services/polkit/qml.cpp
Outdated
| this->bFlow.setBinding([impl]() -> AuthFlow* { return impl->activeFlow().value(); }); | ||
| this->bIsActive.setBinding([impl]() -> bool { return impl->activeFlow().value() != nullptr; }); | ||
| this->bIsRegistered.setBinding([impl]() -> bool { return impl->isRegistered().value(); }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the return types here or can it just be [impl] {}?
src/services/polkit/qml.cpp
Outdated
| QBindable<AuthFlow*> PolkitAgent::flow() { return &this->bFlow; } | ||
| QBindable<bool> PolkitAgent::isActive() { return &this->bIsActive; } | ||
| QBindable<bool> PolkitAgent::isRegistered() { return &this->bIsRegistered; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can go in the header
src/services/polkit/qml.hpp
Outdated
| //! Contains interface to instantiate a PolKit agent listener. | ||
| class PolkitAgent | ||
| : public QObject | ||
| , public QQmlParserStatus { | ||
| Q_OBJECT; | ||
| QML_ELEMENT; | ||
| Q_INTERFACES(QQmlParserStatus); | ||
| Q_DISABLE_COPY_MOVE(PolkitAgent); | ||
|
|
||
| // clang-format off | ||
| /// The D-Bus path that this agent listener will use. | ||
| /// | ||
| /// If not set, a default of /org/quickshell/Polkit will be used. | ||
| Q_PROPERTY(QString path READ path WRITE setPath); | ||
|
|
||
| /// Indicates whether the agent registered successfully and is in use. | ||
| Q_PROPERTY(bool isRegistered READ default NOTIFY isRegisteredChanged BINDABLE isRegistered); | ||
|
|
||
| /// Indicates an ongoing authentication request. | ||
| /// | ||
| /// If this is true, other properties such as @@message and @@iconName will | ||
| /// also be populated with relevant information. | ||
| Q_PROPERTY(bool isActive READ default NOTIFY isActiveChanged BINDABLE isActive); | ||
|
|
||
| /// The current authentication state if an authentication request is active. | ||
| /// Null when no authentication request is active. | ||
| Q_PROPERTY(AuthFlow* flow READ default NOTIFY flowChanged BINDABLE flow); | ||
| // clang-format on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment formatting broken, and it looks like you dont need clang-format off for this class.
src/services/polkit/flow.cpp
Outdated
| const QString& AuthFlow::message() const { return this->mRequest->message; } | ||
| const QString& AuthFlow::iconName() const { return this->mRequest->iconName; } | ||
| const QString& AuthFlow::actionId() const { return this->mRequest->actionId; } | ||
| const QString& AuthFlow::cookie() const { return this->mRequest->cookie; } | ||
| const QList<Identity*>& AuthFlow::identities() const { return this->mIdentities; } | ||
|
|
||
| QBindable<Identity*> AuthFlow::selectedIdentity() { return &this->bSelectedIdentity; } | ||
|
|
||
| void AuthFlow::setSelectedIdentity(Identity* identity) { | ||
| if (this->bSelectedIdentity.value() == identity) return; | ||
| if (!identity) { | ||
| qmlWarning(this) << "Cannot set selected identity to null."; | ||
| return; | ||
| } | ||
| this->bSelectedIdentity = identity; | ||
| this->currentSession->cancel(); | ||
| this->setupSession(); | ||
| } | ||
|
|
||
| QBindable<bool> AuthFlow::isResponseRequired() { return &this->bIsResponseRequired; } | ||
| QBindable<QString> AuthFlow::inputPrompt() { return &this->bInputPrompt; } | ||
| QBindable<bool> AuthFlow::responseVisible() { return &this->bResponseVisible; } | ||
| QBindable<QString> AuthFlow::supplementaryMessage() { return &this->bSupplementaryMessage; } | ||
| QBindable<bool> AuthFlow::supplementaryIsError() { return &this->bSupplementaryIsError; } | ||
| QBindable<bool> AuthFlow::isCompleted() { return &this->bIsCompleted; } | ||
| QBindable<bool> AuthFlow::isSuccessful() { return &this->bIsSuccessful; } | ||
| QBindable<bool> AuthFlow::isCancelled() { return &this->bIsCancelled; } | ||
| AuthRequest* AuthFlow::authRequest() const { return this->mRequest; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bindables inline in header
src/services/polkit/flow.cpp
Outdated
| this->currentSession->respond(value); | ||
|
|
||
| this->bIsResponseRequired = false; | ||
| this->bInputPrompt = QString {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QString() constructor
src/services/polkit/flow.hpp
Outdated
| Q_OBJECT_BINDABLE_PROPERTY( | ||
| AuthFlow, | ||
| Identity*, | ||
| bSelectedIdentity, | ||
| &AuthFlow::selectedIdentityChanged | ||
| ); | ||
| Q_OBJECT_BINDABLE_PROPERTY( | ||
| AuthFlow, | ||
| bool, | ||
| bIsResponseRequired, | ||
| &AuthFlow::isResponseRequiredChanged | ||
| ); | ||
| Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, QString, bInputPrompt, &AuthFlow::inputPromptChanged); | ||
| Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bResponseVisible, &AuthFlow::responseVisibleChanged); | ||
| Q_OBJECT_BINDABLE_PROPERTY( | ||
| AuthFlow, | ||
| QString, | ||
| bSupplementaryMessage, | ||
| &AuthFlow::supplementaryMessageChanged | ||
| ); | ||
| Q_OBJECT_BINDABLE_PROPERTY( | ||
| AuthFlow, | ||
| bool, | ||
| bSupplementaryIsError, | ||
| &AuthFlow::supplementaryIsErrorChanged | ||
| ); | ||
| Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCompleted, &AuthFlow::isCompletedChanged); | ||
| Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsSuccessful, &AuthFlow::isSuccessfulChanged); | ||
| Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCancelled, &AuthFlow::isCancelledChanged); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-format off here please to keep these inline
|
Minor style question: I have moved the bindable getters to the header, should I do the same for other property getters as well? Other than that I believe I got to everything. I've left it as separate commits for now to make it easier to review, but I can squash later. |
|
Generally my policy is one-line getter functions can go in the header. Takeover change looks fine here. |
|
Alright, thanks! I went through and moved all one-line getters to the headers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm at this point, testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test issues discovered:
- Polkit agent does not attempt to register itself if it failed and then the currently active agent leaves the bus. See notifications.
- Debug log levels are still enabled
- Supplementary error message is never populated for bad passwords
Also noticed: for bindables use Qt::beginPropertyUpdateGroup / end update group to not send useless updates.
This is true. I didn't tackle that before since it seemed to add significant complexity for relatively little advantage. However, I see the
My bad. I misunderstood how the logging categories worked. I thought the default log level was a runtime switch, not in the code.
True. That was an oversight when designing
Aha! I was looking for something like that, thanks! |
|
For the log levels just add QtWarningMsg to the logging category declaration to set the default. QT_LOGGING_RULES is then usable as a runtime switch. |
|
I have looked into registering once the active agent unregisters. Unfortunately, there is no specified bus names that agents need to register. They simply need to have an object on the system bus implementing the agent interface. Since agents generally deny any introspection request on the system bus, we cannot reliably determine which object or service we need to wait for. Also, the Polkit authority does not provide any methods for obtaining the active agent, neither over D-Bus nor via other APIs. At best, we could come up with some heuristics based on running processes or simple timeouts. At that point, I would seriously question whether that is reasonable and worth it. |
This implements a new service module that enables users to write PolKit agents by integrating with
libpolkitandlibpolkitagent. A test configuration is added to the source.Status: I've done everything I have planned.
Original description
This implements a new service module that enables users to write PolKit agents. To this end, I'm integrating with the existing
polkit-qt-1library that is also used by the KDE agent.A relatively minimal example configuration using this new service can be found here.
There's a minor TODO left around cleanup of the
Identityobjects that I haven't been able to get quite right due to my inexperience with Qt. I'm hoping you might have a clean solution for this.Closes #271.